﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace ParallelRuntimeLibrary.Internal
{
    /// <summary>
    /// Work-Stealing Scheduler implementation
    /// </summary>
    internal sealed class WSScheduler
    {
        private uint mProcessorCount;
        private LockedQueue<Task> mGlobalTasks;
        private WThread[] mThreads;
        internal LockedQueue<WThread> mLazyThreads;

        internal bool mStarted;

        internal uint PricessorCount { get { return mProcessorCount; } }

        /*/////////////////////////////////////////////////////////////////////////////*/

        internal WSScheduler()
        {
            mProcessorCount = ResourceManager.RM.PhysicalProcessorCount + 1;
            mGlobalTasks = new LockedQueue<Task>();
            mLazyThreads = new LockedQueue<WThread>();
            mThreads = new WThread[mProcessorCount];
            
            byte[] hardwareThreads = ResourceManager.RM.RequestHardwareThreads(mProcessorCount);

            for (int i = 0; i < mThreads.Length; ++i)
            {
                mThreads[i] = new WThread(hardwareThreads[i], this);
                mLazyThreads.Enqueue(mThreads[i]);
            }
        }

        /*/////////////////////////////////////////////////////////////////////////////*/

        internal void PushTask(Task task, PushTaskOptions option)
        {
            WThread OwnerThread = null;

            if (task.Option == TaskCreationOptions.Detached)
            {
                task.ChoiceId(null);

                // New code that will check if there are lazy/suspended thread and then push the task direct to them
                // Instead of push it inot the golbal queue.
                WThread lazyThread;
                if (mLazyThreads.Dequeue(out lazyThread))
                {
                    task.Status = TaskStatus.Queued;
                    lazyThread.StartWithTask(task);
                }
                else
                {
                    mGlobalTasks.Enqueue(task);
                    task.Status = TaskStatus.Queued;
                }
                // end of new code

            }
            else
            {
                OwnerThread = GetOwnerThread();

                if (OwnerThread == null)
                {
                    task.ChoiceId(null);

                    WThread lazyThread;
                    if (!mStarted && mLazyThreads.Dequeue(out lazyThread))
                    {
                        task.Status = TaskStatus.Queued;
                        lazyThread.StartWithTask(task);
                    }
                    else
                    {
                        mStarted = true;
                        mGlobalTasks.Enqueue(task);
                        task.Status = TaskStatus.Queued;
                    }

                    task.Status = TaskStatus.Queued;
                }
                else
                {
                    task.ChoiceId(OwnerThread.CurrentTaskId);
                    OwnerThread.LocalPush(task);
                    task.Status = TaskStatus.Queued;
                }
            }

            // Start Fast Context Execution for effective wait
            if (option != PushTaskOptions.None && OwnerThread != null)
                OwnerThread.FastContextExecution(task);
        }

        //internal void PushTask(Task[] tasks, PushTaskOptions option)
        //{
        //    WThread OwnerThread = GetOwnerThread();

        //    for (int i = 0; i < tasks.Length; i++)
        //    {
        //        if (tasks[i].Option == TaskCreationOptions.Detached || OwnerThread == null)
        //        {
        //            tasks[i].ChoiceId(null);

        //            WThread lazyThread;
        //            if (!mStarted && mLazyThreads.Dequeue(out lazyThread))
        //            {
        //                tasks[i].Status = TaskStatus.Queued;
        //                lazyThread.StartWithTask(tasks[i]);
        //            }
        //            else
        //            {
        //                mStarted = true;
        //                mGlobalTasks.Enqueue(tasks[i]);
        //            }
        //        }
        //        else
        //        {
        //            tasks[i].ChoiceId(OwnerThread.CurrentTaskId);
        //            OwnerThread.LocalPush(tasks[i]);
        //        }
        //        tasks[i].Status = TaskStatus.Queued;
        //    }

        //    if (!mStarted)
        //        StartAllSuspendedThreads();

        //    // Start Fast Context Execution for effective wait
        //    if (option == (option & (PushTaskOptions.WaitAll | PushTaskOptions.WaitFirst)))
        //    {
        //        if (OwnerThread != null)
        //        {
        //            if (option == PushTaskOptions.WaitFirst)
        //                OwnerThread.FastContextExecution(tasks, 0,1);
        //            else if (option == PushTaskOptions.WaitAll)
        //                OwnerThread.FastContextExecution(tasks, 0, tasks.Length);
        //        }
        //    }
        //}

        internal void PushTask(Task[] tasks, PushTaskOptions option)
        {
            int length = tasks.Length;
            WThread OwnerThread = GetOwnerThread();

            if (OwnerThread != null && option == PushTaskOptions.WaitAll)
            {
                int i = 0;
                do
                {
                    WThread tmp;
                    if (i < length && mLazyThreads.Dequeue(out tmp))
                    {
                        tasks[i].ChoiceId(OwnerThread.CurrentTaskId);
                        tasks[i].Status = TaskStatus.Queued;
                        tmp.StartWithTask(tasks[i]);
                        i++;
                    }
                    else
                        break;

                } while (true);

                for (int ii = i; ii < length; ii++)
                {
                    tasks[i].ChoiceId(OwnerThread.CurrentTaskId);
                    OwnerThread.LocalPush(tasks[i]);                    
                    tasks[ii].Status = TaskStatus.Queued;
                }

                OwnerThread.FastContextExecution(tasks, i, length);

                return;
            }


            for (int i = 0; i < tasks.Length; i++)
            {
                if (tasks[i].Option == TaskCreationOptions.Detached || OwnerThread == null)
                {
                    tasks[i].ChoiceId(null);

                    WThread lazyThread;
                    if (!mStarted && mLazyThreads.Dequeue(out lazyThread))
                    {
                        tasks[i].Status = TaskStatus.Queued;
                        lazyThread.StartWithTask(tasks[i]);
                    }
                    else
                    {
                        mStarted = true;
                        mGlobalTasks.Enqueue(tasks[i]);
                    }
                }
                else
                {
                    tasks[i].ChoiceId(OwnerThread.CurrentTaskId);
                    OwnerThread.LocalPush(tasks[i]);
                }
                tasks[i].Status = TaskStatus.Queued;
            }

            if (!mStarted)
                StartAllSuspendedThreads();

            // Start Fast Context Execution for effective wait
            if (option == (option & (PushTaskOptions.WaitAll | PushTaskOptions.WaitFirst)))
            {
                if (OwnerThread != null)
                {
                    if (option == PushTaskOptions.WaitFirst)
                        OwnerThread.FastContextExecution(tasks, 0, 1);
                    else if (option == PushTaskOptions.WaitAll)
                        OwnerThread.FastContextExecution(tasks, 0, tasks.Length);
                }
            }
        }

        private WThread GetOwnerThread()
        {
            int CurrentThreadId = Thread.CurrentThread.ManagedThreadId;
            for (int i = 0; i < mThreads.Length; i++)
            {
                if (mThreads[i].Id == CurrentThreadId)
                    return mThreads[i];
            }
            return null;
        }

        public void AbortTask(Task task)
        {
            task.Status = TaskStatus.Canceled;
            task.NotifyChildrenForCancellation();

            for (int i = 0; i < mThreads.Length; i++)
            {
                lock (mThreads[i])
                    mThreads[i].Abort(task.Identity);
            }
            
        }

        /// <summary>
        /// Try to pop task from the global queue, or steal task from another thread.
        /// </summary>
        /// <returns>Task object if there are Tasks, or null</returns>
        internal Task PopTask()
        {
            Task T = null;

            if (mGlobalTasks.Dequeue(out T))
                return T;
            else
            {
                for (int i = 0; i < mThreads.Length; i++)
                {
                    if (mThreads[i].ThreadState == (ThreadState.Suspended | ThreadState.Background))
                    {
                        if (i > 1) break;
                        else continue;
                    }

                    T = mThreads[i].StealTask();

                    if (T != null)
                        return T;
                }
                return null;
            }

        }

        /*/////////////////////////////////////////////////////////////////////////////*/


        private void StartAllWorkerThreads()
        {
            for (int i = 0; i < mThreads.Length; i++)
            {
                if (mThreads[i].ThreadState == ThreadState.Background) continue;

                if (mThreads[i].ThreadState == (ThreadState.Unstarted | ThreadState.Background))
                    mThreads[i].Start();
                else if (mThreads[i].ThreadState == (ThreadState.Suspended | ThreadState.Background))
                    mThreads[i].Resume();
                else if (mThreads[i].ThreadState == (ThreadState.Aborted | ThreadState.Background))
                {
                    mThreads[i].Restart();
                    mThreads[i].Start();
                }
            }
            mStarted = true;
        }

        private void StartAllSuspendedThreads()
        {
            do
            {
                WThread tmp;
                if (mLazyThreads.Dequeue(out tmp))
                {
                    if (tmp.ThreadState == (ThreadState.Unstarted | ThreadState.Background))
                        tmp.Start();
                    else if (tmp.ThreadState == (ThreadState.Suspended | ThreadState.Background))
                        tmp.Resume();
                    else if (tmp.ThreadState == (ThreadState.Aborted | ThreadState.Background))
                    {
                        tmp.Restart();
                        tmp.Start();
                    }
                }
                else
                {
                    mStarted = true;
                    return;
                }

            } while (true);
        }
    }
}
